// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ILLink.Shared.DataFlow;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FlowAnalysis;
using Microsoft.CodeAnalysis.Operations;
using StateValue = ILLink.RoslynAnalyzer.DataFlow.LocalDataFlowState<
    ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>,
    ILLink.RoslynAnalyzer.DataFlow.FeatureContext,
    ILLink.Shared.DataFlow.ValueSetLattice<ILLink.Shared.DataFlow.SingleValue>,
    ILLink.RoslynAnalyzer.DataFlow.FeatureContextLattice
    >;

namespace ILLink.RoslynAnalyzer.DataFlow
{
    // Visits a conditional expression to optionally produce a 'FeatureChecksValue'
    // (a set features that are checked to be enabled or disabled).
    // The visitor takes a LocalDataFlowState as an argument, allowing for checks that
    // depend on the current dataflow state.
    internal sealed class FeatureChecksVisitor : OperationVisitor<StateValue, FeatureChecksValue>
    {
        private DataFlowAnalyzerContext _dataFlowAnalyzerContext;

        public FeatureChecksVisitor(DataFlowAnalyzerContext dataFlowAnalyzerContext)
        {
            _dataFlowAnalyzerContext = dataFlowAnalyzerContext;
        }

        public override FeatureChecksValue DefaultVisit(IOperation operation, StateValue state)
        {
            // Visiting a non-understood pattern should return the empty set of features, which will
            // prevent this check from acting as a guard for any feature.
            return FeatureChecksValue.None;
        }

        public override FeatureChecksValue VisitArgument(IArgumentOperation operation, StateValue state)
        {
            return Visit(operation.Value, state);
        }

        public override FeatureChecksValue VisitPropertyReference(IPropertyReferenceOperation operation, StateValue state)
        {
            // A single property may serve as a feature check for multiple features.
            FeatureChecksValue featureChecks = FeatureChecksValue.None;
            foreach (var analyzer in _dataFlowAnalyzerContext.EnabledRequiresAnalyzers)
            {
                if (analyzer.IsFeatureGuard(operation.Property, _dataFlowAnalyzerContext.Compilation))
                {
                    var featureCheck = new FeatureChecksValue(analyzer.RequiresAttributeFullyQualifiedName);
                    featureChecks = featureChecks.And(featureCheck);
                }
            }

            return featureChecks;
        }

        public override FeatureChecksValue VisitUnaryOperator(IUnaryOperation operation, StateValue state)
        {
            if (operation.OperatorKind is not UnaryOperatorKind.Not)
                return FeatureChecksValue.None;

            FeatureChecksValue context = Visit(operation.Operand, state);
            return context.Negate();
        }

        public override FeatureChecksValue VisitLiteral(ILiteralOperation operation, StateValue state)
        {
            // 'false' can guard any feature
            if (GetConstantBool(operation.ConstantValue) is false)
                return FeatureChecksValue.All;

            return FeatureChecksValue.None;
        }

        public static bool? GetLiteralBool(IOperation operation)
        {
            if (operation is not ILiteralOperation literal)
                return null;

            return GetConstantBool(literal.ConstantValue);
        }

        private static bool? GetConstantBool(Optional<object?> constantValue)
        {
            if (!constantValue.HasValue || constantValue.Value is not bool value)
                return null;

            return value;
        }

        public override FeatureChecksValue VisitBinaryOperator(IBinaryOperation operation, StateValue state)
        {
            bool expectEqual;
            switch (operation.OperatorKind)
            {
                case BinaryOperatorKind.Equals:
                    expectEqual = true;
                    break;
                case BinaryOperatorKind.NotEquals:
                    expectEqual = false;
                    break;
                default:
                    return FeatureChecksValue.None;
            }

            if (GetLiteralBool(operation.LeftOperand) is bool leftBool)
            {
                FeatureChecksValue rightValue = Visit(operation.RightOperand, state);
                return leftBool == expectEqual
                    ? rightValue
                    : rightValue.Negate();
            }

            if (GetLiteralBool(operation.RightOperand) is bool rightBool)
            {
                FeatureChecksValue leftValue = Visit(operation.LeftOperand, state);
                return rightBool == expectEqual
                    ? leftValue
                    : leftValue.Negate();
            }

            return FeatureChecksValue.None;
        }

        public override FeatureChecksValue VisitIsPattern(IIsPatternOperation operation, StateValue state)
        {
            if (GetExpectedValueFromPattern(operation.Pattern) is not bool patternValue)
                return FeatureChecksValue.None;

            FeatureChecksValue value = Visit(operation.Value, state);
            return patternValue
                ? value
                : value.Negate();


            static bool? GetExpectedValueFromPattern(IPatternOperation pattern)
            {
                switch (pattern)
                {
                    case IConstantPatternOperation constantPattern:
                        return GetConstantBool(constantPattern.Value.ConstantValue);
                    case INegatedPatternOperation negatedPattern:
                        return !GetExpectedValueFromPattern(negatedPattern.Pattern);
                    default:
                        return null;
                }
            }
        }
    }
}
